﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Lifetime;
using System.Text;
using System.Diagnostics;

namespace Misuzilla.Crigate.Handler
{
    /// <summary>
    /// ハンドラの型情報を読み込むための機能を提供します。
    /// </summary>
    public class HandlerTypeLoader : MarshalByRefObject, IDisposable
    {
        private AppDomain _appDomain;
        private HandlerTypeLoader _handlerLoaderInternal;

        private HandlerTypeLoader(){ }
        private HandlerTypeLoader(HandlerTypeLoader handlerLoaderInternal, AppDomain appDomain)
        {
            _handlerLoaderInternal = handlerLoaderInternal;
            _appDomain = appDomain;
        }
        
        /// <summary>
        /// すべてのアセンブリからハンドラの型情報を収集します。
        /// </summary>
        /// <returns></returns>
        public static HandlerTypeInfoSet LoadTypeInfo()
        {
            HandlerTypeInfoSet handlerTypeInfoSet = new HandlerTypeInfoSet();
            using (HandlerTypeLoader handlerLoader = HandlerTypeLoader.Create())
            {
                handlerTypeInfoSet.Add(handlerLoader.GetHandlerTypeInfoFromAssembly(Assembly.GetExecutingAssembly().FullName));

                String addinsBase = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Handlers");
                if (Directory.Exists(addinsBase))
                {
                    foreach (String fileName in Directory.GetFiles(addinsBase, "*.dll"))
                    {
                        try
                        {
                            handlerTypeInfoSet.Add(handlerLoader.GetHandlerTypeInfoFromAssemblyFile(fileName));
                        }
                        catch (Exception e)
                        {
                            Trace.WriteLine(e);
                        }
                    }
                }
            }
            return handlerTypeInfoSet;
        }
        
        /// <summary>
        /// インスタンスを生成します。
        /// </summary>
        /// <returns></returns>
        public static HandlerTypeLoader Create()
        {
            Type typeHandlerLoader = typeof(HandlerTypeLoader);
            AppDomainSetup appDomainSetup = new AppDomainSetup
                                                {
                                                    ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
                                                };
            AppDomain appDomain = AppDomain.CreateDomain("Crigate HandlerType Loader AppDomain", null, appDomainSetup);
            appDomain.Load(typeHandlerLoader.Assembly.FullName);

            HandlerTypeLoader handlerLoaderInternal = appDomain.CreateInstanceAndUnwrap(typeHandlerLoader.Assembly.FullName,
                                                                                    typeHandlerLoader.FullName,
                                                                                    true,
                                                                                    BindingFlags.Instance | BindingFlags.NonPublic,
                                                                                    null,
                                                                                    new object[0],
                                                                                    null,
                                                                                    null,
                                                                                    null) as HandlerTypeLoader;
            return new HandlerTypeLoader(handlerLoaderInternal, appDomain);
        }
        
        ~HandlerTypeLoader()
        {
            Dispose();
        }
        
        /// <summary>
        /// 指定された名前のアセンブリに含まれるハンドラ情報を取得します。
        /// </summary>
        /// <param name="asmName"></param>
        /// <returns></returns>
        public HandlerTypeInfo[] GetHandlerTypeInfoFromAssembly(String asmName)
        {
            return _handlerLoaderInternal.GetHandlerTypeInfoFromAssemblyInternal(asmName);
        }
        
        /// <summary>
        /// 指定されたパスのアセンブリに含まれるハンドラ情報を取得します。
        /// </summary>
        /// <param name="asmName"></param>
        /// <returns></returns>
        public HandlerTypeInfo[] GetHandlerTypeInfoFromAssemblyFile(String asmFilePath)
        {
            return _handlerLoaderInternal.GetHandlerTypeInfoFromAssemblyFileInternal(asmFilePath);
        }

        #region Internal Implementation
        internal HandlerTypeInfo[] GetHandlerTypeInfoFromAssemblyInternal(String asmName)
        {
            return GetHandlerTypeInfoFromAssemblyInternal(Assembly.LoadWithPartialName(asmName));
        }
        internal HandlerTypeInfo[] GetHandlerTypeInfoFromAssemblyFileInternal(String asmFilePath)
        {
            return GetHandlerTypeInfoFromAssemblyInternal(Assembly.LoadFile(asmFilePath));
        }
        internal HandlerTypeInfo[] GetHandlerTypeInfoFromAssemblyInternal(Assembly asm)
        {
            Debug.Assert(_handlerLoaderInternal == null);
            Debug.Assert(AppDomain.CurrentDomain.FriendlyName == "Crigate HandlerType Loader AppDomain");
            
            List<HandlerTypeInfo> typeInfoList = new List<HandlerTypeInfo>();

            Type typeHandler = typeof (IHandler);
            foreach (Type t in asm.GetTypes())
            {
                if (!typeHandler.IsAssignableFrom(t))
                    continue;

                AppDomainAttribute appDomainAttr = AttributeUtil.GetAttribute<AppDomainAttribute>(t);
                ActivateContextAttribute activateCtxAttr = AttributeUtil.GetAttribute<ActivateContextAttribute>(t);

                typeInfoList.Add(new HandlerTypeInfo
                                     {
                                         Assembly                = asm.FullName,
                                         CodeBase                = asm.CodeBase,
                                         Type                    = t.FullName,
                                         ActivateContext         = (activateCtxAttr != null) ? activateCtxAttr.ActivateContext : ActivateContexts.Manual,
                                         IsAutoActivate          = (activateCtxAttr != null),
                                         IsRequireIsolatedDomain = (appDomainAttr is IsolatedAppDomainAttribute),
                                         IsRequireParentDomain   = (appDomainAttr is ParentAppDomainAttribute),
                                         SharedAppDomainName     = ((appDomainAttr is SharedAppDomainAttribute) ? ((SharedAppDomainAttribute) appDomainAttr).AppDomainName : null)
                                     });
            }
            return typeInfoList.ToArray();
        }
        #endregion

        #region IDisposable メンバ

        public void Dispose()
        {
            lock (this)
            {
                if (_appDomain != null)
                {
                    AppDomain.Unload(_appDomain);
                    _appDomain = null;
                }
            }
        }

        #endregion
    }

}
